<?php
/*
PHP REST SQL: A HTTP REST interface to MySQL written in PHP
Copyright (C) 2004 Paul James <paul@peej.co.uk>
Copyright (C) 2012 Florian Knodt <adlerweb@adlerweb.info>
Copyright (C) 2013 Jochen Zimmermann <zet4080@gmx.de>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
 * PHP REST SQL class
 * The base class for the Rest SQL system that opens up a REST interface to a 
 * MySQL database.
 * The original PHP Rest SQL was a general purpose interface, this version is
 * specific to the Dojo JSONRestStore
 * Original PHP REST SQL class can be found at https://github.com/peej/php-rest-sql
 * Dojo Toolkit can be found at http://www.dojotoolkit.org
 */
class PHPRestSQL {
    
    /**
     * Parsed configuration file
     * @var str[]
     */
    var $config;
    
    /**
     * Database connection
     * @var resource
     */
    var $db;
    
    /**
     * The HTTP request method used.
     * @var str
     */
    var $method = 'GET';
	
    /**
     * The HTTP request data sent (if any).
     * @var str
     */
    var $requestData = NULL;
	
	/**
	 * The URL extension stripped off of the request URL
	 * @var str
	 */
	var $extension = NULL;
	
    /**
     * The database table to query.
     * @var str
     */
    var $table = NULL;

    /**
     * The primary key of the database row to query.
     * @var str[]
     */
    var $uid = NULL;
    
    /**
     * Array of strings to convert into the HTTP response.
     * @var str[]
     */
    var $output = NULL;
    
    /**
     * Requested sorting
     */
    var $sort = NULL;
    
   /**
     * Requested limits
     */
    var $limit = NULL;   
    
   /**
     * filter
     */
    var $filter = NULL;     
    
    /**
     * Total size of the result set
     */
    var $resultSize = NULL;
    
    /**
     * Constructor. Parses the configuration file "phprestsql.ini", grabs any 
     * request data sent, records the HTTP request method used and parses the 
     * request URL to find out the requested table name and primary key values.
     * @param str iniFile Configuration file to use
     */
    function PHPRestSQL($iniFile = 'phprestsql.ini') {
        
        $this->config = parse_ini_file($iniFile, TRUE);
        $this->connect();
        
        if (isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD'])) {
            
            $this->method = $_SERVER['REQUEST_METHOD'];
            
            //==================================================================
            // Read the content, if any content is sent and decode it as json.
            //==================================================================
            
            $stringData = '';
            if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 0) {
                $httpContent = fopen('php://input', 'r');
                while ($data = fread($httpContent, 1024)) {
                    $stringData .= $data;
                }
                fclose($httpContent);
            }
            $this->requestData = json_decode($stringData, true);
            
            //==================================================================
            // Check the url string
            //==================================================================
            // subtract the base url
            $urlString = substr($_SERVER['REQUEST_URI'], strlen($this->config['settings']['baseURL']));
            
            // split up the query part
            $urlQueryParts = explode('?', $urlString);

            // split the url into its parts, remove the leading empty part, if
            // it is empty (subtracting the base url might result in something
            // like /phprestsql/data/, and exploding this gives an empty first
            // and an empty last entry            
			$urlParts = explode('/', $urlQueryParts[0]);
			if (isset($urlParts[0]) && $urlParts[0] == '') {
				array_shift($urlParts);
			}
            if (isset($urlParts[0])) {
                $last = array_pop($urlParts);
                if ($last != '') {
                  $urlParts[] = $last;
                }
            }
			
			// the first none empty part is the table name
            if (isset($urlParts[0])) {
                $this->table = array_shift($urlParts);
            }
            
            // The other parts, if present, are the values of the primary
            // key of the table. Save them and generate the where clause
            if (count($urlParts) > 0 && $urlParts[0] != '') {
               $this->uid = $urlParts[0];
            }
            
            if (isset($urlQueryParts[1]) && $urlQueryParts[1] != '') {
            	
            	// parse_str parses the query part to an object ....          
            	
            	parse_str($urlQueryParts[1], $filter);
           
            	// change the + or - to ASC or DESC                         
            	
            	if (isset($filter['sort'])) {            	
                    $dir  = substr($filter['sort'], 0, 1);
                    $sort = substr($filter['sort'], 1);
                    if ($dir == "-") {
                        $this->sort = $sort . " DESC";  
                    } else {
                        $this->sort = $sort . " ASC";
                    }
                    unset($filter['sort']);
                }
                
                // If there are filters left, save them
                
                if (count($filter) > 0) {
                    $this->filter = $filter;
                }
             	
            }
            
            if (isset($this->uid)) {
                $pk = $this->db->getPrimaryKey($this->table);
                $this->filter[$pk] = $this->uid;
            }
            
            // =================================================================
            // Finally check the headers
            // =================================================================
            // look for Range Header
            
            $header = getallheaders();
            if (isset($header['Range'])) {
                parse_str($header['Range']);
                if (isset($items)) {
                    $this->limit = str_replace('-', ',', $items);
                }
            }            
            
        }
    }
    
    /**
     * Connect to the database.
     */
    function connect() {
        $database = $this->config['database']['type'];
        require_once($database.'.php');
        $this->db = new $database(); 
        if (isset($this->config['database']['username']) && isset($this->config['database']['password'])) {
            if (!$this->db->connect($this->config['database'])) {
                trigger_error('Could not connect to server', E_USER_ERROR);
            }
        } elseif (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
			$this->config['database']['username'] = $_SERVER['PHP_AUTH_USER'];
			$this->config['database']['password'] = $_SERVER['PHP_AUTH_PW'];
            if (!$this->db->connect($this->config['database'])) {
                $this->unauthorized();
                exit;
            }
        } else {
            $this->unauthorized();
            exit;
        }
    }
    
    /**
     * Execute the request.
     */
    function exec() {
        
        switch ($this->method) {
            case 'GET':
                $this->get();
                break;
            case 'POST':
                $this->post();
                break;
            case 'PUT':
                $this->put();
                break;
            case 'DELETE':
                $this->delete();
                break;
        }
   
        $this->db->close();
        
    }
    
    /**
     * Execute a GET request. A GET request fetches a list of tables when no 
     * table name is given, a list of rows when a table name is given, or a 
     * table row when a table and primary key(s) are given. It does not change 
     * the database contents.
     */
    function get() {
        
        if ($this->table) {
            $output = $this->db->get($this->table, $this->filter, $this->sort, $this->limit);
            $this->generateResponseData($output);
        } else {
          $this->notFound();
        }
    }

    /**
     * Execute a POST request.
     */
    function post() {
        if ($this->table) { // insert a row without a uid

            if ($this->requestData) {
                $row = $this->db->insertRow($this->table, $this->requestData);
                if ($row) {
                    $this->created($this->config['settings']['baseURL'].'/'.$this->table.'/'.$this->db->lastInsertId().'/');
                    $this->generateResponseData($row);
                } else {
                    $this->internalServerError();
                }
            } else {
                $this->lengthRequired();
            }
        } else {
            $this->methodNotAllowed('GET, HEAD');
        }
    }

    /**
     * Execute a PUT request. A PUT request adds a new row to a table given a 
     * table and name=value pairs in the request body.
     */
    function put() {
        if ($this->table && $this->uid) {
            if ($this->requestData) {

                $updatedRow = $this->db->updateRow($this->table, $this->uid, $this->requestData);
                
                if ($updatedRow) {
                    if (count($updatedRow["rows"]) > 0) {
                        $this->ok();
                        $this->generateResponseData($updatedRow);
                    } else {
                        $this->badRequest();
                    }
                } else {
                    $this->internalServerError();
                }
                
            } else {
                $this->lengthRequired();
            }
        } elseif ($this->table) {
            $this->methodNotAllowed('GET, HEAD, PUT');
        } else {
            $this->methodNotAllowed('GET, HEAD');
        }
    }
	
    /**
     * Execute a DELETE request. A DELETE request removes a row from the database given a table and primary key(s).
     */
    function delete() {
        if ($this->table && $this->uid) {
            $resource = $this->db->deleteRow($this->table, $this->uid);
            if ($resource) {
                if ($this->db->numAffected() > 0) {
                    $this->noContent();
                } else {
                    $this->notFound();
                }
            } else {
                $this->unauthorized();
            }
        } elseif ($this->table) {
            $this->methodNotAllowed('GET, HEAD, PUT');
        } else {
            $this->methodNotAllowed('GET, HEAD');
        }
    }
    
    /**
     * Generate the HTTP response data.
     */
    function generateResponseData($output) {
        header('Content-Type: application/json');
        if (isset($this->limit)) {
            header(sprintf('Content-Range: items %s/%s',str_replace(',','-', $this->limit), $output["total"]));
        }
        echo json_encode($output["rows"]);
    }
    
    /**
     * Send a HTTP 200 response header.
     */
    function ok() {
        header('HTTP/1.0 200 OK');
    }    
        
    /**
     * Send a HTTP 201 response header.
     */
    function created($url = FALSE) {
        header('HTTP/1.0 201 Created');
        if ($url) {
            header('Location: '.$url);   
        }
    }
    
    /**
     * Send a HTTP 204 response header.
     */
    function noContent() {
        header('HTTP/1.0 204 No Content');
    }
    
    /**
     * Send a HTTP 400 response header.
     */
    function badRequest() {
        header('HTTP/1.0 400 Bad Request');
    }
    
    /**
     * Send a HTTP 401 response header.
     */
    function unauthorized($realm = 'PHPRestSQL') {
        header('WWW-Authenticate: Basic realm="'.$realm.'"');
        header('HTTP/1.0 401 Unauthorized');
    }
    
    /**
     * Send a HTTP 404 response header.
     */
    function notFound() {
        header('HTTP/1.0 404 Not Found');
    }
    
    /**
     * Send a HTTP 405 response header.
     */
    function methodNotAllowed($allowed = 'GET, HEAD') {
        header('HTTP/1.0 405 Method Not Allowed');
        header('Allow: '.$allowed);
    }
    
    /**
     * Send a HTTP 406 response header.
     */
    function notAcceptable() {
        header('HTTP/1.0 406 Not Acceptable');
        echo join(', ', array_keys($this->config['renderers']));
    }
    
    /**
     * Send a HTTP 411 response header.
     */
    function lengthRequired() {
        header('HTTP/1.0 411 Length Required');
    }
    
    /**
     * Send a HTTP 500 response header.
     */
    function internalServerError() {
        header('HTTP/1.0 500 Internal Server Error');
    }
    
}

?>
